package buckets

import (
	"encoding/xml"
	"io"
	"net/http"
	"strconv"

	"github.com/gin-gonic/gin"

	"jdfs2/internal/api/common"
	"jdfs2/internal/service"
	"jdfs2/internal/utils"
)

// RegisterRoutes registers bucket-related routes
func RegisterRoutes(api *gin.RouterGroup, s3svc service.S3Service) {
	// List buckets
	api.GET("/", listBuckets(s3svc))

	// Bucket-level operations
	api.GET("/:bucket", getBucket(s3svc))
	api.PUT("/:bucket", createBucket(s3svc))
	api.POST("/:bucket", postBucket(s3svc))
	api.DELETE("/:bucket", deleteBucket(s3svc))
	api.HEAD("/:bucket", headBucket(s3svc))
}

// listBuckets handles GET /
func listBuckets(s3svc service.S3Service) gin.HandlerFunc {
	return func(c *gin.Context) {
		buckets, err := s3svc.ListBuckets(c)
		if err != nil {
			common.WriteS3Error(c, http.StatusInternalServerError, "InternalError", "Failed to list buckets")
			return
		}
		var resp common.ListAllMyBucketsResult
		for _, b := range buckets {
			resp.Buckets = append(resp.Buckets, common.ListAllMyBucketsBucket{Name: b.Name, CreationDate: b.CreationDate.UTC()})
		}
		c.Header("Content-Type", "application/xml")
		_ = xml.NewEncoder(c.Writer).Encode(resp)
	}
}

// getBucket handles GET /:bucket with various query parameters
func getBucket(s3svc service.S3Service) gin.HandlerFunc {
	return func(c *gin.Context) {
		bucket := c.Param("bucket")

		// ?uploads
		if _, ok := c.GetQuery("uploads"); ok {
			prefix := c.Query("prefix")
			maxUploads := 1000
			if v := c.Query("max-uploads"); v != "" {
				if n, err := strconv.Atoi(v); err == nil && n > 0 {
					maxUploads = n
				}
			}
			uploads, truncated, err := s3svc.ListMultipartUploads(c, bucket, prefix, maxUploads)
			if err != nil {
				if err == utils.ErrNoSuchBucket {
					common.WriteS3Error(c, http.StatusNotFound, "NoSuchBucket", "The specified bucket does not exist.")
				} else {
					common.WriteS3Error(c, http.StatusInternalServerError, "InternalError", err.Error())
				}
				return
			}
			var out common.ListMultipartUploadsResult
			out.Bucket = bucket
			out.MaxUploads = maxUploads
			out.IsTruncated = truncated
			for _, u := range uploads {
				out.Uploads = append(out.Uploads, common.UploadSummary{
					Key:       u.Key,
					UploadId:  u.UploadID,
					Initiated: u.Initiated.UTC(),
				})
			}
			c.Header("Content-Type", "application/xml")
			_ = xml.NewEncoder(c.Writer).Encode(out)
			return
		}

		// ?location
		if _, ok := c.GetQuery("location"); ok {
			region, err := s3svc.GetBucketLocation(c, bucket)
			if err != nil {
				if err == utils.ErrNoSuchBucket {
					common.WriteS3Error(c, http.StatusNotFound, "NoSuchBucket", "The specified bucket does not exist.")
				} else {
					common.WriteS3Error(c, http.StatusInternalServerError, "InternalError", err.Error())
				}
				return
			}
			type loc struct {
				XMLName xml.Name `xml:"LocationConstraint"`
				Value   string   `xml:",chardata"`
			}
			c.Header("Content-Type", "application/xml")
			_ = xml.NewEncoder(c.Writer).Encode(loc{Value: region})
			return
		}

		// ?versioning
		if _, ok := c.GetQuery("versioning"); ok {
			status, err := s3svc.GetBucketVersioning(c, bucket)
			if err != nil {
				common.WriteS3Error(c, http.StatusNotFound, "NoSuchBucket", "The specified bucket does not exist.")
				return
			}
			type ver struct {
				XMLName xml.Name `xml:"VersioningConfiguration"`
				Status  string   `xml:"Status,omitempty"`
			}
			c.Header("Content-Type", "application/xml")
			_ = xml.NewEncoder(c.Writer).Encode(ver{Status: status})
			return
		}

		// ?policy
		if _, ok := c.GetQuery("policy"); ok {
			policy, err := s3svc.GetBucketPolicy(c, bucket)
			if err != nil {
				if err == utils.ErrNoSuchBucketPolicy {
					common.WriteS3Error(c, http.StatusNotFound, "NoSuchBucketPolicy", "The specified bucket does not have a bucket policy.")
				} else if err == utils.ErrNoSuchBucket {
					common.WriteS3Error(c, http.StatusNotFound, "NoSuchBucket", "The specified bucket does not exist.")
				} else {
					common.WriteS3Error(c, http.StatusInternalServerError, "InternalError", err.Error())
				}
				return
			}
			c.Header("Content-Type", "application/json")
			_, _ = c.Writer.Write([]byte(policy))
			return
		}

		// ?lifecycle
		if _, ok := c.GetQuery("lifecycle"); ok {
			lifecycle, err := s3svc.GetBucketLifecycleConfiguration(c, bucket)
			if err != nil {
				if err == utils.ErrNoSuchLifecycleConfiguration {
					common.WriteS3Error(c, http.StatusNotFound, "NoSuchLifecycleConfiguration", "The lifecycle configuration does not exist.")
				} else if err == utils.ErrNoSuchBucket {
					common.WriteS3Error(c, http.StatusNotFound, "NoSuchBucket", "The specified bucket does not exist.")
				} else {
					common.WriteS3Error(c, http.StatusInternalServerError, "InternalError", err.Error())
				}
				return
			}
			c.Header("Content-Type", "application/xml")
			_, _ = c.Writer.Write([]byte(lifecycle))
			return
		}

		// ?acl
		if _, ok := c.GetQuery("acl"); ok {
			acl, err := s3svc.GetBucketACL(c, bucket)
			if err != nil {
				common.WriteS3Error(c, http.StatusNotFound, "NoSuchBucket", "The specified bucket does not exist.")
				return
			}
			type aclResp struct {
				XMLName xml.Name `xml:"AccessControlPolicy"`
				Canned  string   `xml:"Canned"`
			}
			c.Header("Content-Type", "application/xml")
			_ = xml.NewEncoder(c.Writer).Encode(aclResp{Canned: acl})
			return
		}

		// ListObjectsV2
		if c.Query("list-type") == "2" {
			prefix := c.Query("prefix")
			delimiter := c.Query("delimiter")
			startAfter := c.Query("start-after")
			continuationToken := c.Query("continuation-token")
			maxKeys := 1000
			if v := c.Query("max-keys"); v != "" {
				if n, err := strconv.Atoi(v); err == nil && n > 0 {
					maxKeys = n
				}
			}
			res, err := s3svc.ListObjectsV2(c, bucket, prefix, delimiter, startAfter, continuationToken, maxKeys)
			if err != nil {
				if err == utils.ErrNoSuchBucket {
					common.WriteS3Error(c, http.StatusNotFound, "NoSuchBucket", "The specified bucket does not exist.")
				} else {
					common.WriteS3Error(c, http.StatusInternalServerError, "InternalError", err.Error())
				}
				return
			}
			var out common.ListObjectsV2Result
			out.Name = res.Name
			out.Prefix = res.Prefix
			out.Delimiter = res.Delimiter
			out.MaxKeys = res.MaxKeys
			out.KeyCount = res.KeyCount
			out.IsTruncated = res.IsTruncated
			out.ContinuationToken = c.Query("continuation-token")
			out.NextContinuationToken = res.NextContinuationToken
			for _, ctt := range res.Contents {
				out.Contents = append(out.Contents, common.ListObjectV2Content{Key: ctt.Key, Size: ctt.Size, ETag: "\"" + ctt.ETag + "\"", LastModified: ctt.LastModified.UTC()})
			}
			for _, cp := range res.CommonPrefixes {
				out.CommonPrefixes = append(out.CommonPrefixes, common.ListCommonPrefix{Prefix: cp})
			}
			c.Header("Content-Type", "application/xml")
			_ = xml.NewEncoder(c.Writer).Encode(out)
			return
		}

		common.WriteS3Error(c, http.StatusNotImplemented, "NotImplemented", "Unsupported bucket GET operation")
	}
}

func postBucket(s3svc service.S3Service) gin.HandlerFunc {
	return func(c *gin.Context) {
		bucket := c.Param("bucket")

		// ?delete -> DeleteObjects
		if _, ok := c.GetQuery("delete"); ok {
			var req common.DeleteRequest
			if err := xml.NewDecoder(c.Request.Body).Decode(&req); err != nil {
				common.WriteS3Error(c, http.StatusBadRequest, "MalformedXML", "The XML you provided was not well-formed.")
				return
			}
			var res common.DeleteResult
			for _, o := range req.Objects {
				if err := s3svc.DeleteObject(c, bucket, o.Key); err != nil {
					res.Errors = append(res.Errors, common.DeleteError{Key: o.Key, Code: "NoSuchKey", Message: "The specified key does not exist."})
				} else if !req.Quiet {
					res.Deleted = append(res.Deleted, common.DeletedObject{Key: o.Key})
				}
			}
			c.Header("Content-Type", "application/xml")
			_ = xml.NewEncoder(c.Writer).Encode(res)
			return
		}

		common.WriteS3Error(c, http.StatusNotImplemented, "NotImplemented", "Unsupported bucket POST operation")
	}
}

// createBucket handles PUT /:bucket
func createBucket(s3svc service.S3Service) gin.HandlerFunc {
	return func(c *gin.Context) {
		bucket := c.Param("bucket")

		// ?versioning
		if _, ok := c.GetQuery("versioning"); ok {
			var req struct {
				XMLName xml.Name `xml:"VersioningConfiguration"`
				Status  string   `xml:"Status"`
			}
			if err := xml.NewDecoder(c.Request.Body).Decode(&req); err != nil {
				common.WriteS3Error(c, http.StatusBadRequest, "MalformedXML", "The XML you provided was not well-formed.")
				return
			}
			if err := s3svc.PutBucketVersioning(c, bucket, req.Status); err != nil {
				common.WriteS3Error(c, http.StatusNotFound, "NoSuchBucket", "The specified bucket does not exist.")
				return
			}
			c.Status(http.StatusOK)
			return
		}

		// ?lifecycle
		if _, ok := c.GetQuery("lifecycle"); ok {
			body, err := io.ReadAll(c.Request.Body)
			if err != nil {
				common.WriteS3Error(c, http.StatusBadRequest, "InvalidRequest", "Failed to read request body.")
				return
			}

			// Validate the lifecycle configuration by parsing it
			var lifecycleConfig common.LifecycleConfiguration
			if err := xml.Unmarshal(body, &lifecycleConfig); err != nil {
				common.WriteS3Error(c, http.StatusBadRequest, "MalformedLifecycleConfiguration", "The lifecycle configuration is malformed.")
				return
			}

			if err := s3svc.PutBucketLifecycleConfiguration(c, bucket, string(body)); err != nil {
				if err == utils.ErrNoSuchBucket {
					common.WriteS3Error(c, http.StatusNotFound, "NoSuchBucket", "The specified bucket does not exist.")
				} else {
					common.WriteS3Error(c, http.StatusInternalServerError, "InternalError", err.Error())
				}
				return
			}
			c.Status(http.StatusOK)
			return
		}

		// ?policy
		if _, ok := c.GetQuery("policy"); ok {
			body, err := io.ReadAll(c.Request.Body)
			if err != nil {
				common.WriteS3Error(c, http.StatusBadRequest, "InvalidRequest", "Failed to read request body.")
				return
			}
			if err := s3svc.PutBucketPolicy(c, bucket, string(body)); err != nil {
				if err == utils.ErrNoSuchBucket {
					common.WriteS3Error(c, http.StatusNotFound, "NoSuchBucket", "The specified bucket does not exist.")
				} else {
					common.WriteS3Error(c, http.StatusInternalServerError, "InternalError", err.Error())
				}
				return
			}
			c.Status(http.StatusOK)
			return
		}

		// ?acl
		if _, ok := c.GetQuery("acl"); ok {
			body, err := io.ReadAll(c.Request.Body)
			if err != nil {
				common.WriteS3Error(c, http.StatusBadRequest, "InvalidRequest", "Failed to read request body.")
				return
			}
			// For simplicity, extract canned ACL from XML or use simple string
			acl := string(body)
			if err := s3svc.PutBucketACL(c, bucket, acl); err != nil {
				if err == utils.ErrNoSuchBucket {
					common.WriteS3Error(c, http.StatusNotFound, "NoSuchBucket", "The specified bucket does not exist.")
				} else {
					common.WriteS3Error(c, http.StatusInternalServerError, "InternalError", err.Error())
				}
				return
			}
			c.Status(http.StatusOK)
			return
		}

		// Default: create bucket
		if err := s3svc.CreateBucket(c, bucket); err != nil {
			// map to XML error (simple mapping)
			common.WriteS3Error(c, http.StatusConflict, "BucketAlreadyOwnedByYou", "The requested bucket name is not available.")
			return
		}
		c.Status(http.StatusOK)
	}
}

// deleteBucket handles DELETE /:bucket
func deleteBucket(s3svc service.S3Service) gin.HandlerFunc {
	return func(c *gin.Context) {
		bucket := c.Param("bucket")

		// ?lifecycle
		if _, ok := c.GetQuery("lifecycle"); ok {
			if err := s3svc.DeleteBucketLifecycleConfiguration(c, bucket); err != nil {
				if err == utils.ErrNoSuchBucket {
					common.WriteS3Error(c, http.StatusNotFound, "NoSuchBucket", "The specified bucket does not exist.")
				} else {
					common.WriteS3Error(c, http.StatusInternalServerError, "InternalError", err.Error())
				}
				return
			}
			c.Status(http.StatusNoContent)
			return
		}

		// ?policy
		if _, ok := c.GetQuery("policy"); ok {
			if err := s3svc.DeleteBucketPolicy(c, bucket); err != nil {
				if err == utils.ErrNoSuchBucket {
					common.WriteS3Error(c, http.StatusNotFound, "NoSuchBucket", "The specified bucket does not exist.")
				} else {
					common.WriteS3Error(c, http.StatusInternalServerError, "InternalError", err.Error())
				}
				return
			}
			c.Status(http.StatusNoContent)
			return
		}

		// Default: delete bucket
		if err := s3svc.DeleteBucket(c, bucket); err != nil {
			if err == utils.ErrNoSuchBucket {
				common.WriteS3Error(c, http.StatusNotFound, "NoSuchBucket", "The specified bucket does not exist.")
				return
			}
			if err == utils.ErrBucketNotEmpty {
				common.WriteS3Error(c, http.StatusConflict, "BucketNotEmpty", "The bucket you tried to delete is not empty.")
				return
			}
			common.WriteS3Error(c, http.StatusInternalServerError, "InternalError", err.Error())
			return
		}
		c.Status(http.StatusNoContent)
	}
}

// headBucket handles HEAD /:bucket
func headBucket(s3svc service.S3Service) gin.HandlerFunc {
	return func(c *gin.Context) {
		bucket := c.Param("bucket")
		if err := s3svc.HeadBucket(c, bucket); err != nil {
			common.WriteS3Error(c, http.StatusNotFound, "NoSuchBucket", "The specified bucket does not exist.")
			return
		}
		c.Status(http.StatusOK)
	}
}
